Skip to content
/ rfcs Public

[RFC 0199] sandbox nix evaluator#199

Closed
bglgwyng wants to merge 1 commit intoNixOS:masterfrom
bglgwyng:sandbox-nix-evaluator
Closed

[RFC 0199] sandbox nix evaluator#199
bglgwyng wants to merge 1 commit intoNixOS:masterfrom
bglgwyng:sandbox-nix-evaluator

Conversation

@bglgwyng
Copy link

@bglgwyng bglgwyng commented Mar 3, 2026

This RFC proposes that the Nix evaluator should not be treated as a special, privileged component. Instead, it should run inside a sandbox and interact with the store exclusively through the daemon socket — the same way builders already do via Recursive Nix. This generalizes the evaluator into just another program that talks to the Nix daemon from inside a sandbox.
This generalization enables:

  • OS-enforced purity for evaluation, replacing trust in language-level semantics
  • A path toward any language (not just Nix) producing derivations as a first-class frontend, by speaking the same daemon protocol from inside the same sandbox
  • IFD and Recursive Nix to become ordinary daemon protocol interactions rather than special cases

Rendered: 00199-sandbox-nix-evaluator.md


## What changes for the evaluator

In multi-user mode, the evaluator already uses the daemon protocol for store operations. What changes is the **execution environment**: the evaluator process is now confined to a sandbox with no network access and restricted filesystem access. `builtins.fetchurl` and `builtins.path` continue to go through the daemon — but now this is the *only* way to perform I/O, enforced by the sandbox rather than by language semantics.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

builtins.fetchurl and builtins.path […] now this is the only way[s] to perform I/O”

For builtins.path, the evaluator process performs the actual file read or at least needs to open the file and pass the fd to the daemon. Otherwise, as I've touched on above, we would read everything as root.

Also note that forcing something akin to builtins.path (i.e. copying to store) for readFile and import would likely create an unreasonable overhead.


## What happens to IFD and Recursive Nix

They stop being special cases. IFD is currently "evaluation pauses, triggers a build, resumes with the result" — a special code path. Under this proposal, the evaluator simply sends a build request through the daemon socket and reads back the output path. This is an ordinary protocol interaction, the same thing builders already do with Recursive Nix. The distinction between "evaluation-time store access" and "build-time store access" disappears because both go through the same socket in the same kind of sandbox.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not believe you are describing this correctly. Evaluation pauses in the IFD case not because of some “special case", but because the evaluator has to wait for the file it wants to read to become available (and can't do anything else because it isn't multithreaded), so nothing would change, but we would also not gain anything.

|-- evaluator runs inside sandbox
|-- daemon socket mounted (same mechanism as builds)
|-- no network access
|-- filesystem restricted to source inputs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really the main problem of this proposal and will need to be elaborated on.

It is completely impossible to determine the “source inputs” of an evaluation without performing said evaluation.

For that purpose, it is actually better that the evaluator has full file system access (at least to read) since performing those reads over the daemon protocol would mean that we would need to add an interface for performing arbitrary filesystem reads to the daemon which runs as reads. This would create the potential for new exploits.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it's mentioned above that the relevant information comes from the CLI, I'm guessing "source inputs" just means "initial source files to be evaluated" e.g. a flake. And that evaluation would determine if anything needs to be fetched. This is further supported by what is said in the examples. So, if that's what it means, it isn't impossible but we'll need a round trip once we find anything that needs to be fetched before further evaluation, yes?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly speaking, the initial input you learn from the CLI is a single file. While evaluating it, you will learn about more files you need to read in order to properly evaluate it (in contrast to a less dynamic language with a module system or similar where you would be able to find this information statically).

With flakes and/or pure eval, you would be able to determine a maximum set of available files for evaluation and make them available to the evaluator in the sandbox. The slight problem being, of course, that neither is a stable feature of Nix in the first place. Also, in order for the proposal to be properly implemented based on those feature, flakes and pure eval would not only need to be stabilized, but also the only evaluation mode available which is not a desirable goal in the first place.

- **Protocol commitment.** A stable daemon protocol is a long-term maintenance burden.
- **Community.** This deliberately levels the Nix language's special status, which may face resistance.

# Alternatives
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you just run the evaluator inside a user space sandbox of some description to get the same guarantees?

- **Protocol commitment.** A stable daemon protocol is a long-term maintenance burden.
- **Community.** This deliberately levels the Nix language's special status, which may face resistance.

# Alternatives
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you just run the evaluator inside a user space sandbox of some description to get the same guarantees?


## What changes for alternative frontends

Nothing special. Any program that can speak the daemon protocol through a Unix socket can produce derivations. A Python script, a Rust binary, a Guile program — all run inside the same sandbox with the same daemon socket. The Nix language becomes one frontend among many, distinguished only by the size of its ecosystem (nixpkgs), not by architectural privilege.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also do not fully understand this angle. This proposal is not strictly necessary for alternative frontends to be feasible.


## What changes for alternative frontends

Nothing special. Any program that can speak the daemon protocol through a Unix socket can produce derivations. A Python script, a Rust binary, a Guile program — all run inside the same sandbox with the same daemon socket. The Nix language becomes one frontend among many, distinguished only by the size of its ecosystem (nixpkgs), not by architectural privilege.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also do not fully understand this angle. This proposal is not strictly necessary for alternative frontends to be feasible.


This asymmetry gives the Nix language a privileged position:

- `nix build`, `nix develop`, `nix flake` — all CLI entry points invoke the Nix evaluator. There is no sanctioned path to the store that does not go through the Nix language.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. nix-store --add on a valid derivation works.
  2. nix derivation add takes JSON and creates derivations.
  3. Guix was in fact sharing the daemon and the store just fine for some time, until they decided to rewrite it.

- **Performance.** Sandboxing the evaluator adds overhead for sandbox creation and teardown. In single-user mode, where the evaluator currently accesses the store directly (bypassing the daemon), switching to daemon-mediated access would add per-call latency. In multi-user mode the daemon path is already used, so the additional cost is only the sandbox itself.
- **Bootstrapping.** The evaluator binary may reside in the store. The daemon must make it available inside the sandbox before evaluation starts, similar to how it provisions builders.
- **Protocol commitment.** A stable daemon protocol is a long-term maintenance burden.
- **Community.** This deliberately levels the Nix language's special status, which may face resistance.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this lose the current fetch-without-checksum-and-cache functionality?

@7c6f434c
Copy link
Member

7c6f434c commented Mar 3, 2026

So, of the three goals:

  • Purity can be enforced with user-side sandboxing
    • The current purity story is complicated enough that baking it deep in is a problem not a solution
  • Generating .drvs via daemon protocol is easy (if you already have the content of the derivations) with the current stable Nix, and will allso be easy with the stabilisation of nix derivation part of the CLI.
  • Recursive Nix / Dynamic Derivations seem to stand to benefit more from some work done of them, not from scope creep.

@bglgwyng
Copy link
Author

bglgwyng commented Mar 4, 2026

Thank you all for the thoughtful feedback — it was genuinely helpful.

After reflecting on the discussion, I've realized that the use case I had in mind doesn't actually require what this RFC proposes. My motivation was enabling a non-Nix language to leverage nixpkgs and other Nix-defined packages via Recursive Nix builds. I assumed the evaluator's I/O builtins would need to be rerouted through the daemon to work inside a build sandbox, but that turns out to be unnecessary:

  • import and readFile work fine as long as the source tree is added to the store and included as a build input — which is the correct approach anyway.
  • builtins.fetchurl and other network-dependent builtins can be covered by fixed-output derivations.

On the bright side, this means Recursive Nix is already good enough to enable non-Nix language interop with the Nix ecosystem without any changes to the evaluator. That's a nice place to be.

Closing this RFC. Thanks again for taking the time to review it.

@bglgwyng bglgwyng closed this Mar 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants